<?php
/**
 * 
 * Parser to extract type info and wsdl methods
 * @author colin
 *
 */
class CZ_Tools_WSDLParser{
    /* @var $soapClient soapClient */
    private $soapClient=NULL;
    
    public function __construct($wsdl){
        $this->soapClient = new SoapClient($wsdl, array('wsdl'=>$wsdl));
    }
    /**
     * 
     * Get soap client
     * @return SoapClient
     */
    protected function getClient(){
        return $this->soapClient;
    }
    protected function trim($str){
        return trim($str, " \t {}[];()");
    }
    
    /**
     * 
     * Get types within wsdl
     * @return array
     * something like array(array('name'=>'ModelName', 'members'=>array('name'=>'type'))
     */
    public function getTypeList(){
        $client = $this->getClient();
        $rawTypes = $client->__getTypes();
        $resultTypes = array();
        foreach ($rawTypes as $type){
            $lines = explode("\n", $type);
            if(count($lines) == 0){
                continue;
            }
            $type = $this->getType($lines);
            if(null == $type){
                continue;
            }
            $resultTypes[] = $type;
        }
        return $resultTypes;
    }
    /**
     * 
     * Get method list of wsdl
     * @return array
     * something linke array(array('name'=>'methodName', 'paras'=>array('name'=>'type'),'return'=>'return type')
     */
    public function getMethodList(){
        $methods = $this->getClient()->__getFunctions();
        $methodResult = array();
        foreach($methods as $line){
            $typeNameParts = explode(" ", $line,2);
            if(count($typeNameParts) <= 1){
                continue;
            }
            //Return type of method
            $returnType = $typeNameParts[0];
            $method =$typeNameParts[1];
            $method = $this->trim($method);
            $nameParasPart = explode('(', $method);
            if(count($nameParasPart) <= 1){
                continue;
            }
            //method name
            $methodName = $nameParasPart[0];
            $paraString = $nameParasPart[1];
            $paraArray = explode(',', $paraString);
            //printf("para string: %s, array: %s\n",var_export($paraString,true), var_export($paraArray,true));
            $paraList = array();
            foreach($paraArray as $index=>$name){
                $name = $this->trim($name);
                $parts = explode(" ", $name);
                if(count($parts) != 2){
                    continue;
                }
                $name = $parts[1];
                $type = $parts[0];
                $paraList[] = array($this->trim($name)=>$this->trim($type));
            }
            $methodResult[] = array("name"=>$methodName, 'return'=>$returnType,'paras'=>$paraList);
        }
        return $methodResult;
    }
    /**
     * 
     * Apply model name change to both model definition and method definition
     * @param array $modelMapping	array('old model name'=>'new model name');
     * @param array $data	model definition or method definition
     * @param	string	$type	'model' or 'method'
     */
    public static function applyModeNameChange($modelMapping, &$data, $type="model"){
        $mappingWithLowerCase = array_change_key_case($modelMapping,CASE_LOWER);
        //turn it into array('new model name'=>'old model name')
        $mappingWithLowerCase = array_flip($mappingWithLowerCase);
        foreach($data as $index=>$value){
            if($type == 'model'){
                $name = strtolower($value['name']);
                $key = array_search($name, $mappingWithLowerCase);
                if($key !== FALSE){
                    $value['name'] = $key;
                }
                $members = $value['members'];
                foreach($members as $i=>$member){
                    $parts = array_keys($member);
                    $varname = strtolower($parts[0]);
                    $vartype = $member[$parts[0]];
                    $vartypename = $vartype;
                    if(is_array($vartype)){
                        $vartypename = strtolower($vartype['type']);
                    }else{
                        $vartypename = strtolower($vartypename);
                    }
                    $key = array_search($vartypename, $mappingWithLowerCase);
                    if($key){
                        if(is_array($vartype)){
                            $value['members'][$i][$parts[0]]['type'] = $key;
                        }else{
                            $value['members'][$i][$parts[0]] = $key;
                        }
                    }
                }
            }else{
                $return = '';
                if(array_key_exists('return', $value) && $value['return']){
                    if(is_string($value['return'])){
                        $return = strtolower($value['return']);
                    }else if(is_array($value['return'])){
                        $return = strtolower($value['return']['type']);
                    }
                }
                $key = array_search($return, $mappingWithLowerCase);
                if($key){
                    if(is_string($value['return'])){
                        $value['return'] = $key;
                    }else if(is_array($value['return'])){
                        $value['return']['type'] = $key;
                    }
                }
                $paras = array();
                if(array_key_exists('paras', $value)){
                    $paras = $value['paras'];
                }
                foreach($paras as $i=>$datatype){
                    $parts = array_keys($datatype);
                    $vartype = $datatype[$parts[0]];
                    if(is_array($datatype[$parts[0]])){
                        $vartype = strtolower($datatype[$parts[0]]['type']);
                    }else{
                        $vartype = strtolower($vartype);
                    }
                    $key = array_search($vartype, $mappingWithLowerCase);
                    if($key){
                        if(is_array($datatype[$parts[0]])){
                            $value['paras'][$i][$parts[0]]['type'] = $key;
                        }else{
                            $value['paras'][$i][$parts[0]] = $key;
                        }
                    }
                }
            }
            $data[$index] = $value;
        }
        return $data;
    }
    /**
     * 
     * Generate model class for type
     * @param	array	$type
     * @param	string	$baseClass
     * @return	string
     */
    public static function generateCodeForType($type, $baseClass=NULL){
        $extend = "";
        if ($baseClass){
            $extend = sprintf(" extends %s{", $baseClass);
        }
        $definition = sprintf("<?php\nclass %s%s", $type['name'], $extend);
            $members = $type['members'];
            $prefix = "\n\t";
            foreach($members as $member){
                $parts = array_keys($member);
                $name = $parts[0];
                $vartype = $member[$name];
                $datatype = $vartype;
                if(is_array($vartype)){
                    if(array_key_exists('isarray', $vartype) && $vartype['isarray']){
                        $datatype = sprintf("array contains %s", $vartype['type']);
                    }
                }
                $definition .= sprintf("%s/* @var \$%s %s */", $prefix, $name, $datatype);
                $definition .= sprintf("%spublic $%s;", $prefix, $name);
            }
        $definition .= "\n}";
        return $definition;
    }
    /**
     * 
     * Generate code 
     * @param array $methods
     * @param string $className
     * @param string $baseClass
     * @return string
     */
    public static function generateCodeForMethods($methods, $className, $classMap,$baseClass=NULL, $wsdl=NULL){
        $extend = "";
        if ($baseClass){
            $extend = sprintf(" extends %s{", $baseClass);
        }else{
            $baseClass = "SoapClient";
            $extend = sprintf(" extends %s{", $baseClass);
        }
        $definition = sprintf("<?php\nclass %s%s", $className, $extend);
        $prefix = "\n\t";
            $definition .= $prefix.'private static $instance = NULL;';
            //Classmap
            $definition .= $prefix.sprintf("private static \$classmap = array(");
            foreach($classMap as $old=>$new){
                $definition .= $prefix.sprintf("\t'%s' => '%s',", $old, $new);
            }
            $definition .= $prefix.sprintf(");");
            
            //Constructor
            $definition .= $prefix.sprintf("public function %s(){", $className);
            $definition .= $prefix."\t//TODO:Write your code here";
            $definition .= $prefix."\t\$wsdl=NULL;";
            $wsdlOption = $wsdl?sprintf("'wsdl'=>%s", $wsdl):'';
            $definition .= $prefix.sprintf("\tparent::__construct(\$wsdl, array('classmap'=>self::\$classmap, %s));", $wsdlOption);
            $definition .= $prefix."}";
            
            //methods
            foreach($methods as $m){
                $paras = $m['paras'];
                $returnType = $m['return'];
                //Comments
                $definition .= $prefix.sprintf("/**");
                $definition .= $prefix.sprintf("* %s", $m['name']);
                foreach($paras as $index=>$type){
                    $name = array_keys($type);
                    $name = $name[0];
                    $type = $type[$name];
                    $datatype = $type;
                    if(is_array($type)){
                        if(array_key_exists('isarray', $type) && $type['isarray']){
                            $datatype = sprintf("array contains %s", $type['type']);
                        }
                    }
                    $definition .= $prefix.sprintf("* @param %s %s", str_ireplace(' = NULL', '', $name), $datatype);
                }
                if($returnType){
                    $definition .= $prefix.sprintf("* @return %s", $returnType);
                }
                $definition .= $prefix.sprintf("**/");
                $definition .= $prefix.sprintf("public function %s(", $m['name']);
                $parastring = "";
                $parastringWithoutDefault = "";
                foreach($paras as $index=>$type){
                    $name = array_keys($type);
                    $name = $name[0];
                    if($parastring){
                        $parastring .= ", ".$name;
                        $parastringWithoutDefault .= ", ".str_ireplace(' = NULL', '', $name);
                    }else{
                        $parastring = $name;
                        $parastringWithoutDefault = str_ireplace(' = NULL', '', $name);
                    }
                }
                $definition .= $parastring;
                $definition .= "){";
                $definition .= $prefix.sprintf("\t\$result = \$this->__call('%s',array(%s));", $m['name'], $parastringWithoutDefault);
                $definition .= $prefix.sprintf("\treturn \$result;");
                $definition .= $prefix."}";
            }
            
            //Siglton access
            $definition .= $prefix.'/**';
            $definition .= $prefix.'* Get singlton instance';
            $definition .= $prefix.'* @return '.$className;
            $definition .= $prefix.'**/';
            $definition .= $prefix.'public static function getInstance(){';
                $definition .= $prefix."\tif(NULL == self::\$instance){";
                $definition .= $prefix."\t\tself::\$instance = new self();";
                $definition .= $prefix."\t}";
                $definition .= $prefix."\treturn self::\$instance;";
            $definition .= $prefix.'}';
        $definition .= "\n}";
        return $definition;
    }
	/**
     * 
     * Get type definition from lines of wsdl message type
     * @param array $lines
     * @return array of the type definition
     */
    protected function getType($lines){
        $firstline = array_shift($lines);
        $firstline = $this->trim($firstline);
        $typeNameParts = explode(" ", $firstline);
        if(count($typeNameParts) < 2){
            return NULL;
        }
        if(strtolower($this->trim($typeNameParts[0])) != 'struct'){
            return NULL;
        }
        $typeName = $typeNameParts[1];
        $result = array('name'=>$typeName);
        $members = array();
        foreach($lines as $line){
            $line = $this->trim($line);
            if(strlen($line) == 0){
                continue;
            }
            $typeNameParts = explode(" ", $line);
            if(count($typeNameParts) !== 2){
                continue;
            }
            $members[] = array($typeNameParts[1]=>$typeNameParts[0]);
        }
        $result['members'] = $members;
        return $result;
    }
}
?>